using System;

namespace Nyanimals
{    
    /// <summary>
    /// This class handles capping an object to a min and max.
    /// Requires the type argument to have the >, <, and = operators overloaded
    /// </summary>
    public class CLimiter<T>
    {
        public delegate bool  operation(ref T left, ref T right);
        public delegate float findLamda(ref T min, ref T max, ref T val);
        public delegate void  LimitHandler(CLimiter<T> limiter, object obj);
        
        private operation GreaterCheck;
        private operation LesserCheck;
        private operation EqualCheck;
        private findLamda FindLamda;
        private LimitHandler UnderflowHander;
        private LimitHandler OverflowHander;
        private object AttachedFlowObject;
        private bool bWrapAround;
        
        T min;
        T max;
        T val;
        
        // -1 -> on or hit below min
        //  0 -> normal range
        //  1 -> on or hit above max
        int limitStatus;
        
        public T Min
        {
            get { return min;}
            set 
            { 
                min = value; 
                CheckMin();
            }
        }
        public T Max
        {
            get { return max;}
            set 
            { 
                max = value; 
                CheckMax();
            }
        }
        
        public T Value
        {
            get { return val;}
            set 
            {
                val = value;
                CheckMax();
                CheckMin();
            }
        }
        
        public float Lamda
        {
            get { return FindLamda(ref min, ref max, ref val);}
        }
        
        public int Status
        {
            get { return limitStatus;}
        }
        
        public bool Wrap
        {
            get { return bWrapAround;}
            set { bWrapAround = value;}
        }
        
        private void fakeConstructor(T _min, T _max, T _val, operation great, operation less, operation equal, findLamda lamda, bool wrap)
        {
            min = _min;
            max = _max;
            val = _val;
            GreaterCheck = great;
            LesserCheck = less;
            EqualCheck = equal;
            FindLamda = lamda;
            bWrapAround = wrap;
            OverflowHander = null;
            UnderflowHander = null;
            AttachedFlowObject = null;
            CheckMin();
            CheckMax();
        }
        
        public CLimiter(T _min, T _max, T _val, operation great, operation less, operation equal, findLamda lamda, bool wrap)
        {
            fakeConstructor(_min, _max, _val, great, less, equal, lamda, wrap);
        }
        
        public CLimiter(T _min, T _max, T _val, operation great, operation less, operation equal, findLamda lamda)
        {
            fakeConstructor(_min, _max, _val, great, less, equal, lamda, false);
        }        
        public CLimiter(T _min, T _max, T _val, operation great, operation less, operation equal, findLamda lamda, LimitHandler over, LimitHandler under, object o)
        {
            fakeConstructor(_min, _max, _val, great, less, equal, lamda, false);
            OverflowHander = over;
            UnderflowHander = under;                        
            AttachedFlowObject = o;
        }
        
        
        private void CheckMin()
        {
            if(LesserCheck(ref val, ref min))
            {
                if(bWrapAround)
                {
                    val = max;
                }
                else
                {
                    val = min;
                    
                    // underflow check if we have one
                    if(UnderflowHander != null) 
                    {
                        UnderflowHander(this, AttachedFlowObject);
                    }
                }
            }
            MinMaxCheck();
        }
        private void CheckMax()
        {
            if(GreaterCheck(ref val, ref max))
            {
                if(bWrapAround)
                {
                    val = min;
                }
                else
                {
                    val = max;
                    // overflow check if we have one
                    if(OverflowHander != null)
                    {
                        OverflowHander(this, AttachedFlowObject);
                    }
                }
            }
            MinMaxCheck();
        }
        
        // NOTE: useless is bWrap is on
        private void MinMaxCheck()
        {
            // determin the status
            if(EqualCheck(ref val, ref min))
            {
                limitStatus = -1;
            }
            else if(EqualCheck(ref val, ref max))
            {
                limitStatus = 1;
            }
            else
            {
                limitStatus = 0;
            }
            
            // min cannot equal max
            if(EqualCheck(ref min, ref max))
            {
                throw new CLimiterMinMaxException("Min cannot equal Max!");
            }
        }
    }
    
    #region IntLimiter
    public class CIntLimiter
    {
        private CLimiter<int> limiter;
        private static bool g(ref int left, ref int right)
        {
            return left > right;
        }
        private static bool l(ref int left, ref int right)
        {
            return left < right;
        }
        private static bool e(ref int left, ref int right)
        {
            return left == right;
        }
        private static float l(ref int min, ref int max, ref int val)
        {
            float lamda = CMath.GetLamdaAsDecimal((float)min, (float)max, (float)val);
            return lamda;
        }
        
        public CIntLimiter(int min, int max, int val, bool wrap = false)
        {
            limiter = new CLimiter<int>(min, max, val, g, l, e, l, wrap);
        }
        
        public CIntLimiter(int min, int max, int val, CLimiter<int>.LimitHandler over, CLimiter<int>.LimitHandler under, object o)
        {
            limiter = new CLimiter<int>(min, max, val, g, l, e, l, over, under, o);
        }
        
        public int Min
        {
            get { return limiter.Min;}
            set { limiter.Min = value;}
        }
        public int Max
        {
            get { return limiter.Max;}
            set { limiter.Max = value;}
        }
        
        public int Value
        {
            get { return limiter.Value;}
            set {limiter.Value = value;}
        }
        
        public int Status
        {
            get { return limiter.Status;}
        }
        
        public bool Wrap
        {
            get { return limiter.Wrap;}
            set { limiter.Wrap = value;}
        }
        
        public float Lamda
        {
            get { return limiter.Lamda;}
        }
    }
    #endregion
    
    #region FloatLimiter
    public class CFloatLimiter
    {
        private CLimiter<float> limiter;
        private static bool g(ref float left, ref float right)
        {
            return left > right;
        }
        private static bool l(ref float left, ref float right)
        {
            return left < right;
        }
        private static bool e(ref float left, ref float right)
        {
            return left == right;
        }
        private static float l(ref float min, ref float max, ref float val)
        {
            float lamda = CMath.GetLamdaAsDecimal(min, max, val);
            return lamda;
        }
        
        public CFloatLimiter(float min, float max, float val, bool wrap = false)
        {
            limiter = new CLimiter<float>(min, max, val, g, l, e, l, wrap);
        }
        public CFloatLimiter(float min, float max, float val, CLimiter<float>.LimitHandler over, CLimiter<float>.LimitHandler under, object o)
        {
            limiter = new CLimiter<float>(min, max, val, g, l, e, l, over, under, o );
        }
        
        public float Min
        {
            get { return limiter.Min;}
            set { limiter.Min = value;}
        }
        public float Max
        {
            get { return limiter.Max;}
            set { limiter.Max = value;}
        }
        
        public float Value
        {
            get { return limiter.Value;}
            set {limiter.Value = value;}
        }
        
        public int Status
        {
            get { return limiter.Status;}
        }
        
        public float Lamda
        {
            get { return limiter.Lamda;}
        }
        
        public bool Wrap
        {
            get { return limiter.Wrap;}
            set { limiter.Wrap = value;}
        }
        
    }
    #endregion
}

